#ifndef SH_DATETIME_DATETIME_HPP
#define SH_DATETIME_DATETIME_HPP


#include <chrono>
#include <iomanip>
#include <sstream>
#include <ctime>
#include "../time/timestamp.hpp"

namespace sh {

template<class T = std::chrono::milliseconds>
struct DateTime {
    using Rep = typename T::rep;
    using Period = typename T::period;

    DateTime();
    ~DateTime() = default;

    std::string toString(std::string format = "yyyy-MM-dd hh:mm:ss.zzz");
    Rep sinceEpoch() const { return rep; }

    static inline DateTime<T> fromSinceEpoch(Rep timestamp) { return DateTime(timestamp); }
    static inline DateTime<T> currentDateTime() { return DateTime(currentSinceEpoch()); }
    static Rep currentSinceEpoch();

private:
    DateTime(Rep rep) : rep{ rep } {}
private:
    Rep rep;
};

using DateTime_s = DateTime<std::chrono::seconds>;
using DateTime_ms = DateTime<std::chrono::milliseconds>;

/*
* //////////////////////////////////////////////////////////////////////////////////////////
*
*
*
* //////////////////////////////////////////////////////////////////////////////////////////
*/

template<class T>
inline
DateTime<T>::DateTime()
    // : DateTime{ currentSinceEpoch() }
    : DateTime{ 0 }
{

}

template<class T>
inline
std::string 
DateTime<T>::toString(std::string format)
{
    auto fnReplace = [] (std::string& format, std::string const& fromStr, std::string const& toStr) {
        size_t l = 0;
        while (true) {
            size_t r = format.find(fromStr, l);
            if (r == std::string::npos) break;
            if(r > 0 && format.at(r-1) == '%') {
                l = r + 1;
                continue;
            }
            format.replace(r, fromStr.size(), toStr.data(), toStr.size());
            l = r + toStr.size() + 1;
        }
    };

    fnReplace(format, "yyyy", "%Y");
    fnReplace(format, "yy", "%y");
    fnReplace(format, "MM", "%m");
    fnReplace(format, "M", "%m");
    fnReplace(format, "dd", "%d");
    fnReplace(format, "d", "%d");
    fnReplace(format, "hh", "%H");
    fnReplace(format, "h", "%H");
    fnReplace(format, "mm", "%M");
    fnReplace(format, "m", "%M");
    fnReplace(format, "ss", "%S");
    fnReplace(format, "s", "%S");

    auto duration_ms = std::chrono::duration_cast<std::chrono::milliseconds>(T(rep));
    auto ms_count = duration_ms.count() % 1000; // ms

    auto duration_s  = std::chrono::duration_cast<std::chrono::seconds>(T(rep));
    auto time_point = std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>(duration_s);
    std::time_t timeT = std::chrono::system_clock::to_time_t(time_point);
    std::tm tm;
#ifdef _WIN32
    localtime_s(&tm, &timeT);
#else
    localtime_r(&timeT, &tm);
#endif

    std::ostringstream oss;
    oss << std::put_time(&tm, format.c_str());
    std::string str = oss.str();

    std::ostringstream oss_ms;
    oss_ms << std::setw(3) << std::setfill('0') << ms_count;
    fnReplace(str, "zzz", oss_ms.str());

    return str;
}


template<class T>
inline
typename DateTime<T>::Rep
DateTime<T>::currentSinceEpoch() {
    //using namespace std;
    //using namespace std::chrono;
    //return time_point_cast<T>(system_clock::now()).time_since_epoch().count();
    return timestamp::system<T>();
}

} // namespace sh

#endif // SH_DATETIME_DATETIME_HPP
